干货 | MySQL锁之源码探索
姜宇祥,2012年加入携程,10年数据库核心代码开发经验,相关开发涉及达梦,MySQL数据库。现致力于携程MySQL的底层研发,为特殊问题定位和处理提供技术支持。
锁是计算机程序运行时协调并发访问同一数据资源的机制。对于数据库系统来说,数据是一种供许多用户共享的资源,那么如何保证数据并发访问的一致性、有效性是必须解决的一个问题。所以,锁对于数据库来说,是非常重要的一个功能。通过各种锁,实现了数据库事务中的隔离性。本篇文章将从源码层面介绍MySQL的元数据锁和InnoDB的实现。
一、MySQL的架构与锁
MySQL在架构上分为两层,服务和存储引擎层。服务层集中了网络通讯、语法分析和计划生成等通用功能;存储引擎层主要负责数据的存储。元数据的并发管理集中在服务层,数据的并发管理在存储引擎层。因此对于元数据的锁在服务层进行实现,数据的隔离特性在存储引擎层实现。本篇将介绍服务层的元数据锁的实现,以及现下使用率最高的具有ACID特性的InooDB的数据锁。
二、元数据锁
2.1 元数据锁类型
元数据类型 | 说明 |
GLOBAL | 全局锁 |
TABLESPACE | 表空间锁 |
SCHEMA | 数据库锁 |
TABLE | 表锁 |
FUNCTION | 函数锁 |
PROCEDURE | 存储过程锁 |
TRIGGER | 触发器锁 |
EVENT | 事件锁 |
COMMIT | 事务锁,在服务器层进行提交事务时进行上锁 |
USER_LEVEL_LOCK | 用户锁,通过GET_LOCK/RELEASE_LOCK进行获取和释放 |
LOCKING_SERVICE | 安装locking_service.so插件后使用,为用户提供所服务 |
2.2 元数据锁申请与释放
在申请元数据锁的同时,会指定锁释放的时间。在程序执行到指定位置时,如语句执行结束或者事务执行结束,会检查元数据锁的上锁情况,并释放那些需要在该位置释放的元数据锁。
释放类型 | 说明 |
MDL_STATEMENT | 语句执行结束时释放 |
MDL_TRANSACTION | 事务结束时释放 |
MDL_EXPLICIT | 显式释放 |
抽象元数据锁的上锁和释放的过程,整理为如下流程图
2.3 元数据锁关系
MySQL的元数据也是有从属关系的。有些元数据进行上锁的同时,需要配合其他元数据锁,这里称这种关系为从属关系。这种从属关系如下图所示,其箭头所指方向为元数据锁所依赖关系。比如在为SCHEMA元数据加锁时,需要GLOBAL元数据锁。
2.4 元数据锁级别
由于对元数据的访问存在不同的需求,因此设置不同级别锁级别,用于对元数据及数据的访问控制。
级别 | 说明 |
MDL_INTENTION_EXCLUSIVE(IX) | 意向排它锁,可升级为排它锁。可与其他连接的意向排它锁兼容,但不兼容于共享锁。 |
MDL_SHARED(S) | 共享锁 |
MDL_SHARED_HIGH_PRIO(SH) | 高优先级共享锁。 |
MDL_SHARED_READ(SR) | 共享读锁,对表数据存在意向读 |
MDL_SHARED_WRITE(SW) | 共享写锁,对表数据存在意向写 |
MDL_SHARED_WRITE_LOW_PRIO | 低优先级共享写锁 |
MDL_SHARED_UPGRADABLE(SU) | 可升级共享锁 |
MDL_SHARED_READ_ONLY(SRO) | 共享只读锁,该锁将阻塞对表的元信息和数据的更新 |
MDL_SHARED_NO_WRITE(SNW) | 可升级的表锁,该锁将阻塞对表数据的更新,但允许进行表数据的读取 |
MDL_SHARED_NO_READ_WRITE(SNRW) | 可升级的表锁,该锁将阻塞对表数据的读和更新。 |
MDL_EXCLUSIVE(X) | 排它锁 |
2.5 元数据锁源码
该部分介绍MySQL源码的主要源文件和主要函数。其中源文件以mdl.h/mdl.cc为核心,定义了元数据锁的主要数据结构和函数,lock.cc/sql_db.cc等源文件使用元数据锁所定义的数据结构和函数。
主要源文件及其关系如下,蓝框内所列源文件依赖于红框内的源文件所定义的内容。
主要函数如下图关系所列
三、InnoDB锁
3.1 事务隔离级简介
存储引擎InnoDB实现事务的四个隔离级,也就是读未提交和读提交等四个事务隔离级。所谓事务隔离级,是并发访问中控制数据读写的方式。在这里先简单介绍这四个事务隔离级的来龙去脉,以便于理解MySQL的锁机制。
InnoDB事务采用不同的锁机制,会产生不同的现象。
现象 | 说明 |
脏读 | 此时事务忽略其他事务的任何锁,因此可以读取其他事务未提交的修改数据,当其他事务回滚数据时所读取的数据为错误数据。 |
不可重复读 | 在同一事务中,对同一行读取不同的值。此时读取数据时未对数据进行读取保护,故其他事务可修改该事务。 |
幻读 | 在同一事务中,使用相同的过滤条件,获取不同结果集。此时事务读取数据时,未对查询数据进行范围保护。 |
事务隔离级和各种现象的关系,“X”表示在该事务隔离级下现象可发生,“--”表示在该事务隔离级下现象不会发生。
事务隔离级 | 脏读 | 不可重复读 | 幻读 |
读未提交 | X | X | X |
读提交 | -- | X | X |
可重复读 | -- | -- | X |
串行化 | -- | -- | -- |
3.2 InnoDB的锁类型
InnoDB为保护并发访问下的数据,根据不同的粒度对数据进行。
锁类型 | 说明 |
表锁 | 全表上锁,此锁对表中所有数据进行保护 |
行锁 | 单行数据进行保护 |
间隙锁 | 和行锁结合,对行数据进行范围上锁,对该范围数据进行保护 |
3.3 InnoDB的锁级别
InnoDB的锁
锁级别 | 说明 |
意向共享锁(IS) | 用于对表锁,不能用于行锁 |
意向排它锁(IX) | 用于对表锁,不能用于行锁 |
共享锁(S) | 主要用于行锁,只有在“lock tables for read”时用于表锁 |
排它锁(X) | 主要用于行锁,只有在“lock tables for write”时用于表锁 |
自增锁(AI) | 表级锁,用于语句级的MySQL binlog |
各个级别锁之间存在兼容性问题,如下表格列出各个级别锁之间的兼容性。“X”表示不兼容,“O”表示兼容。
IS | IX | S | X | AI | |
IS | O | O | O | X | O |
IX | O | O | X | X | O |
S | O | X | O | X | X |
X | X | X | X | X | X |
AI | O | O | X | X | X |
3.4 间隙锁
InnoDB间隙锁(next-key lock)的用处是在repeatable read的隔离级下防止幻读现象的出现,所以一定要记住,在其他隔离级下是不会出现间隙锁的。间隙锁的原理是在通过对行锁进行特殊标识(此时的行锁就被称为间隙锁),指出在该范围内行记录的左开右闭区间被封锁,不可进行更新操作。正是采用这种针对行记录之间间隙上锁方式,所以称为间隙锁或者next-key锁。
如下图所示,假设索引中的key值为5、17、23和29。当执行如下SQL:begin; select * from t4 where id2=16 forupdate;会对key:17创建一个行锁,并标识该行锁为间隙锁,其锁定区间为红框内6到17,也就是这个区间的值域发生了变化,如果再发生变化可能会影响该区域的数据行集合,所以需要锁定该区域为不可更新。
3.5 源码结构
核心代码包含了有关锁的宏定义和函数定义等锁的类型定义和操作定义,应用代码为使用这些宏和函数的模块。
【推荐阅读】
少量余票,抓紧上车哦
前端大咖&新晋网红
带来最新技术和最佳实践
3月24日,等你来,点击图片报名~